
#include <QPainter>
#include <QScrollBar>
#include <QPaintEvent>
#include <QDebug>

#include "hexwidget.h"

HexWidget::HexWidget(QWidget *parent)
    : QAbstractScrollArea(parent),
    m_size(0),
    m_selectPosition(0),
    m_selectSize(0),
    m_bytesPerLine(16)
{
#ifdef Q_OS_WIN32
    setFont(QFont("Courier", 10));
#else
    setFont(QFont("Monospace", 10));
#endif
    init();
}

HexWidget::~HexWidget()
{
    m_data.clear();
}

void HexWidget::setData(const QByteArray &data)
{
    m_data.clear();
    m_data = data;
    m_size = data.length();
    m_selectPosition = 0;
    m_selectSize = 0;

    init();
    viewport()->update();
}

void HexWidget::setSelect(qint64 pos, int len)
{
    m_selectPosition = pos;
    m_selectSize = len;
    ensureSelectVisible();
    viewport()->update();
}

QByteArray HexWidget::getSelectData()
{
    return m_data.mid(m_selectPosition, m_selectSize);
}

qint64 HexWidget::findBytes(const QByteArray &data, bool next)
{
    if (data.isEmpty() || m_data.isEmpty()) return -1;
    if (next) {
        int index = m_data.indexOf(data, m_selectPosition);
        if (index < 0 || index >= m_selectPosition + m_selectSize) {
            return index;
        }
        return m_data.indexOf(data, m_selectPosition + m_selectSize);
    } else {
        int index = m_selectPosition;
        if (m_data.mid(index, data.length()) == data) {
            index--;
        }
        return m_data.lastIndexOf(data, index);
    }
}

// Handle events

QSize HexWidget::sizeHint() const
{
    int w = m_margin + m_labelWidth + m_HexWidth + m_textWidth;
    return QSize(w + 23, m_lineHeight * 16);
}

void HexWidget::mousePressEvent(QMouseEvent * event)
{
    // 判断是否左键
    if (event->button() != Qt::LeftButton) {
        QAbstractScrollArea::mousePressEvent(event);
        return;
    }

    const int xOffset = horizontalScrollBar()->value();
    int x = event->pos().x() + xOffset;
    x -= m_margin + m_labelWidth + m_margin / 2;
    x /= m_columnWidth;
    if (x < 0) {
        return;
    } else if (x >= m_bytesPerLine) {
        x = event->pos().x() + xOffset;
        x -= m_margin + m_labelWidth + m_HexWidth;
        x /= m_charWidth;
        if (x < 0) x = 0; // 不太可能再小于0了
        else if (x >= m_bytesPerLine) return;
    }

    const int topLine = verticalScrollBar()->value();
    int y = event->pos().y();
    y = y / m_lineHeight + topLine;

    qint64 pos = y * m_bytesPerLine + x;
    emit onSelect(pos);
}

void HexWidget::paintEvent(QPaintEvent *event)
{
    // qDebug() << "HexWidget::" << __func__ << "() in";
    QPainter painter(viewport());
    painter.setBackgroundMode(Qt::TransparentMode);
    const int topLine = verticalScrollBar()->value();
    const int xOffset = horizontalScrollBar()->value();

    // 地址与数据之间的竖线
    const int x1 = -xOffset + m_margin + m_labelWidth - m_margin / 2 - 1;
    const int x2 = x1 + m_HexWidth;
    painter.setPen(palette().color(QPalette::WindowText));
    painter.drawLine(x1, 0, x1, viewport()->height());
    painter.drawLine(x2, 0, x2, viewport()->height());

    // 列条纹
    for (int i = 0; i < m_bytesPerLine; i += 2) {
        int bg_x = -xOffset + m_margin + m_labelWidth + m_columnWidth * i + m_margin / 2;
        QRect r(bg_x, 0, m_columnWidth, viewport()->height());
        painter.fillRect(event->rect() & r, palette().alternateBase());
    }

    // 地址
    painter.setPen(palette().color(QPalette::WindowText));
    for (int i = 0; i < m_numVisibleLines; i++) {
        qint64 pos = m_bytesPerLine * (i + topLine);
        QString address = QString("%1").arg(pos, m_addressBytes, 16, QChar('0')).toUpper();

        if (pos + m_bytesPerLine > m_selectPosition && pos < m_selectPosition + m_selectSize) {
            QRect r(-xOffset + m_margin, m_lineHeight * i, m_labelWidth - m_margin, m_lineHeight);
            painter.save();
            painter.setPen(palette().highlightedText().color());
            painter.fillRect(r, palette().highlight());
            painter.drawText(-xOffset + m_margin, m_lineHeight * i + m_ascent, address);
            painter.restore();
            continue;
        }
        painter.drawText(-xOffset + m_margin, m_lineHeight * i + m_ascent, address);
    }

    // 数据
    painter.setPen(palette().color(QPalette::Text));
    QByteArray data = m_data.mid(topLine * m_bytesPerLine, m_numVisibleLines * m_bytesPerLine);
    int textStart = -xOffset + m_margin + m_labelWidth;
    for (int i = 0; i < m_numVisibleLines; i++) {
        if (m_bytesPerLine * i >= data.length()) break;
        QString hex, text;
        for (int j = 0; j < m_bytesPerLine; j++) {
            if (m_bytesPerLine * i + j >= data.length()) break;
            char c = data[m_bytesPerLine * i + j];
            hex.append(QString(" %1").arg((unsigned char) c, 2, 16, QChar('0')));
            if (c < ' ' || c > '~') c = '.';
            text.append(c);
        }
        hex = hex.toUpper();
        int y = m_lineHeight * i + m_ascent;

        qint64 pos = m_bytesPerLine * (i + topLine);
        if (pos + m_bytesPerLine > m_selectPosition && pos < m_selectPosition + m_selectSize) {
            int start = 0, end = m_bytesPerLine;
            if (pos < m_selectPosition) {
                start = m_selectPosition - pos;
            }
            if (pos + m_bytesPerLine > m_selectPosition + m_selectSize) {
                end = m_selectPosition + m_selectSize - pos;
            }
            int x1 = textStart + m_columnWidth * start;
            int x2 = textStart + m_HexWidth + m_charWidth * start;
            QRect r1(x1 + m_margin / 2, m_lineHeight * i, (end - start) * m_columnWidth, m_lineHeight);
            QRect r2(x2, m_lineHeight * i, (end - start) * m_charWidth, m_lineHeight);

            if (start > 0) {
                painter.drawText(textStart, y, hex.mid(0, start * 3));
                painter.drawText(textStart + m_HexWidth, y, text.mid(0, start));
            }
            painter.save();
            painter.setPen(QPen(Qt::white));
            painter.fillRect(r1, QColor(0x0078D7));
            painter.fillRect(r2, QColor(0x0078D7));
            painter.drawText(x1, y, hex.mid(start * 3, (end - start) * 3));
            painter.drawText(x2, y, text.mid(start, end - start));
            painter.restore();
            if (end < m_bytesPerLine && end < hex.length() / 3) {
                painter.drawText(textStart + m_columnWidth * end, y, hex.mid(end * 3));
                painter.drawText(textStart + m_HexWidth + m_charWidth * end, y, text.mid(end));
            }
        } else {
            painter.drawText(textStart, y, hex);
            painter.drawText(textStart + m_HexWidth, y, text);
        }
    }
    // qDebug() << "HexWidget::" << __func__ << "() out";
}

void HexWidget::resizeEvent(QResizeEvent *)
{
    init();
}

void HexWidget::changeEvent(QEvent *)
{
    init();
    viewport()->update();
}

// Private utility functions

void HexWidget::init()
{
    // qDebug() << "HexWidget::" << __func__ << "() in";
    QFontMetrics fm(fontMetrics());
    m_descent = fm.descent();
    m_ascent = fm.ascent();
    m_lineHeight = fm.lineSpacing();
    m_charWidth = fm.horizontalAdvance(QChar('M'));
    m_margin = fm.horizontalAdvance(QChar(' '));
    m_columnWidth = 2 * m_charWidth + m_margin;
    m_numLines = (m_size - 1) / m_bytesPerLine + 1;
    m_numVisibleLines = (viewport()->height() - 1) / m_lineHeight + 1;
    m_addressBytes = QString("%1").arg(m_size, 0, 16).length();
    if (m_addressBytes < 4) m_addressBytes = 4;
    m_labelWidth = m_addressBytes * m_charWidth + m_margin;
    m_HexWidth = m_bytesPerLine * m_columnWidth + m_margin * 3 / 2;
    m_textWidth = m_bytesPerLine * m_charWidth + m_margin;
    int lineWidth = m_margin +  + m_labelWidth + m_HexWidth + m_textWidth;

    horizontalScrollBar()->setRange(0, lineWidth - viewport()->width());
    horizontalScrollBar()->setPageStep(viewport()->width());
    verticalScrollBar()->setRange(0, m_numLines - m_numVisibleLines + 1);
    verticalScrollBar()->setPageStep(m_numVisibleLines);
    ensureSelectVisible();
    // qDebug() << "HexWidget::" << __func__ << "() out";
}

void HexWidget::ensureSelectVisible()
{
    int xOffset = horizontalScrollBar()->value();
    int xStart = m_margin + m_labelWidth;
    int xEnd = m_margin + m_labelWidth + m_HexWidth + m_textWidth;
    if (m_selectPosition % m_bytesPerLine + m_selectSize <= m_bytesPerLine) {
        // 选中内容只在一行上
        xStart = m_margin + m_labelWidth + (m_selectPosition % m_bytesPerLine) * m_columnWidth;
        xEnd = m_margin + m_labelWidth + m_HexWidth + (m_selectPosition % m_bytesPerLine + m_selectSize) * m_charWidth;
    }
    int topLine = verticalScrollBar()->value();
    int lineStart = m_selectPosition / m_bytesPerLine;
    int lineEnd = (m_selectPosition + m_selectSize - 1) / m_bytesPerLine + 1;

    QRect cr = QRect(xStart - xOffset, (lineStart - topLine) * m_lineHeight,
        xEnd - xStart, (lineEnd - lineStart) * m_lineHeight);
    QWidget *v = viewport();
    if (!v->rect().intersects(cr)) {
        if (cr.top() < v->rect().top())
            verticalScrollBar()->setValue(lineStart);
        else if (cr.bottom() > v->rect().bottom())
            verticalScrollBar()->setValue(lineEnd - m_numVisibleLines + 1);
        if (cr.left() < v->rect().left())
            horizontalScrollBar()->setValue(xStart);
        else if (cr.right() > v->rect().right())
            horizontalScrollBar()->setValue(xEnd - v->rect().width() + 1);
    }
}
